package cn.icanci.loopstack.amc.biz.service.impl;

import cn.hutool.http.Method;
import cn.hutool.json.JSONUtil;
import cn.icanci.loopstack.amc.admin.dal.mongodb.daointerface.AppMockCallDAO;
import cn.icanci.loopstack.amc.admin.dal.mongodb.dataobject.AppMockCallDO;
import cn.icanci.loopstack.amc.biz.event.log.LogEvent;
import cn.icanci.loopstack.amc.biz.mapper.AppMockCallMapper;
import cn.icanci.loopstack.amc.biz.model.AppMockDebugResult;
import cn.icanci.loopstack.amc.biz.model.PublishResource;
import cn.icanci.loopstack.amc.biz.service.AppMockCallService;
import cn.icanci.loopstack.amc.biz.service.BaseService;
import cn.icanci.loopstack.amc.common.enums.LogOperatorTypeEnum;
import cn.icanci.loopstack.amc.common.enums.ModuleTypeEnum;
import cn.icanci.loopstack.amc.common.model.PageList;
import cn.icanci.loopstack.amc.common.model.TextValue;
import cn.icanci.loopstack.amc.common.model.config.AppMockCallVO;
import cn.icanci.loopstack.amc.common.result.R;
import cn.icanci.loopstack.amc.spi.mock.MockCallStandardHandler;
import cn.icanci.loopstack.api.client.Client;
import cn.icanci.loopstack.script.LsiScriptEngine;
import cn.icanci.loopstack.script.compile.GroovyClassLoaderHolder;
import cn.icanci.loopstack.script.context.LsiScriptEngineContext;
import cn.icanci.loopstack.script.enums.ScriptTypeEnum;

import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;
import javax.script.Bindings;
import javax.script.SimpleBindings;

import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * @author icanci
 * @since 1.0 Created in 2023/01/15 13:14
 */
@Service
public class AppMockCallServiceImpl extends BaseService<AppMockCallVO> implements AppMockCallService {
    private static final Logger logger = LoggerFactory.getLogger(AppMockCallServiceImpl.class);
    @Resource
    private AppMockCallDAO      appMockCallDAO;
    @Resource
    private AppMockCallMapper   appMockCallMapper;
    @Resource
    private LsiScriptEngine     lsiScriptEngine;
    @Resource
    private Client              httpClient;
    @Value("${ddk.server}")
    private String              ddkServerPath;
    @Value("${ddk.resource}")
    private String              ddkResource;

    @Override
    public PageList<AppMockCallVO> queryPage(AppMockCallVO web2vo, int currentPage, int pageSize) {
        PageList<AppMockCallDO> pageQuery = appMockCallDAO.pageQuery(appMockCallMapper.vo2do(web2vo), currentPage, pageSize);
        return new PageList<>(appMockCallMapper.dos2vos(pageQuery.getData()), pageQuery.getPaginator());
    }

    @Override
    public void save(AppMockCallVO appMock) {
        if (doInsert(appMock)) {
            AppMockCallDO appMockCallDO = appMockCallMapper.vo2do(appMock);
            appMockCallDAO.insert(appMockCallDO);
            eventDispatcher.fire(new LogEvent(appMockCallDO.getUuid(), ModuleTypeEnum.AMC_APP_MOCK_CALL, JSONUtil.toJsonStr(appMockCallDO), LogOperatorTypeEnum.CREATE));
        } else {
            appMockCallDAO.update(appMockCallMapper.vo2do(appMock));
            eventDispatcher.fire(new LogEvent(appMock.getUuid(), ModuleTypeEnum.AMC_APP_MOCK_CALL, JSONUtil.toJsonStr(appMock), LogOperatorTypeEnum.UPDATE));
        }
    }

    @Override
    public AppMockCallVO validateAppMockCallName(String mockName) {
        return appMockCallMapper.do2vo(appMockCallDAO.queryByAppMockName(mockName));
    }

    @Override
    public List<TextValue> loadSelector() {
        List<AppMockCallDO> appMockList = appMockCallDAO.queryAll();
        if (CollectionUtils.isEmpty(appMockList)) {
            return Lists.newArrayList();
        }
        List<TextValue> textValues = Lists.newArrayList();
        for (AppMockCallDO appMockCallDO : appMockList) {
            String label;
            AppMockCallVO appMockCallVO = appMockCallMapper.do2vo(appMockCallDO);
            if (isDeleted(appMockCallVO)) {
                label = String.format(DELETED_FORMAT, appMockCallVO.getMockName());
            } else {
                label = String.format(NOT_DELETED_FORMAT, appMockCallVO.getMockName());
            }
            textValues.add(new TextValue(label, appMockCallVO.getUuid()));
        }
        return textValues;
    }

    @Override
    public AppMockDebugResult debug(AppMockCallVO appMock, String scriptContentTest) {
        ScriptTypeEnum scriptType = appMock.getScriptType();
        String script = appMock.getScript();
        // mvel2.0 脚本
        if (scriptType == ScriptTypeEnum.MVEL2) {
            return noneHandlerMockDebugResult(scriptContentTest, scriptType, script);
        }

        // groovy脚本
        if (scriptType == ScriptTypeEnum.GROOVY) {
            try {
                Class<?> compile = GroovyClassLoaderHolder.compile(script);
                Object instance = compile.newInstance();
                if (instance instanceof MockCallStandardHandler) {
                    MockCallStandardHandler handler = (MockCallStandardHandler) instance;
                    // 全限定类名重复的处理。此处无需处理，
                    // 经过 GroovyClassLoaderHolder.compile 的class对象是不重复的，即使代码是完全一样的
                    Object execute = handler.execute(scriptContentTest);
                    return AppMockDebugResult.success(scriptType.getDesc(), execute);
                }
            } catch (Exception e) {
                logger.info("[AppMockCallService][debug] error msg:{}", e.getLocalizedMessage());
                return AppMockDebugResult.fail(scriptType.getDesc(), e.getLocalizedMessage());
            }
            return noneHandlerMockDebugResult(scriptContentTest, scriptType, script);
        }

        throw new IllegalArgumentException("Not Support ScriptType: " + scriptType);
    }

    /**
     * 非标准化处理器执行流程
     * 
     * @param scriptContentTest scriptContentTest
     * @param scriptType scriptType
     * @param script script
     * @return script
     */
    private AppMockDebugResult noneHandlerMockDebugResult(String scriptContentTest, ScriptTypeEnum scriptType, String script) {
        Bindings bindings = JSONUtil.toBean(scriptContentTest, SimpleBindings.class);
        LsiScriptEngineContext<Object> context = lsiScriptEngine.eval(scriptType, bindings, script);

        AppMockDebugResult result = new AppMockDebugResult();
        result.setSuccess(context.isSuccess());
        result.setRealResult(context.getRealRetVal());
        if (context.getThrowable() != null) {
            result.setExceptionMessage(context.getThrowable().getLocalizedMessage());
        }
        result.setScriptType(scriptType.getDesc());
        return result;
    }

    @Override
    public R publishMockCall(String uuid) {
        PublishResource publishResource = new PublishResource();
        publishResource.setResource(ddkResource);
        publishResource.setValue(uuid);
        Client.RpcRequest rpcRequest = new Client.RpcRequest(ddkServerPath, publishResource, Maps.newHashMap(), Method.POST, 3, TimeUnit.SECONDS, 0);
        logger.info("[AppMockCallService][publishMockCall] req:{}", JSONUtil.toJsonStr(publishResource));
        R call = httpClient.call(rpcRequest, R.class);
        logger.info("[AppMockCallService][publishMockCall] resp:{}", JSONUtil.toJsonStr(call));
        return call;
    }

}
